﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using log4net;

namespace Jiubang.Collections {
    /// <summary>
    ///   封装LockFreeStack实现的对象池。
    /// </summary>
    /// <typeparam name = "T">对象类型</typeparam>
    internal sealed class LockFreePool<T> : IDisposable where T : class {
        private static readonly ILog _log = LogManager.GetLogger(typeof (LockFreePool<T>));
        private readonly int _limit;
        private readonly Func<T> _onStockout;
        private readonly LockFreeStack<T> _stack;
        private int _available;
        private int _missed;
        private int _taken;

        /// <summary>
        ///   初始化池
        /// </summary>
        /// <param name = "limit">池最大容纳的项数量。超过该值后归还的对象将被抛弃。</param>
        /// <param name = "items">初始化池容纳对象</param>
        /// <param name = "onStockout">池中的项数据不足时创建新对象的方法。</param>
        public LockFreePool(int limit, IEnumerable<T> items, Func<T> onStockout) {
            if (limit <= 0)
                throw new ArgumentOutOfRangeException("limit");

            _limit = limit;
            _stack = new LockFreeStack<T>();

            if (items == null || items.Count() == 0) {
                if (onStockout == null)
                    throw new ArgumentNullException("onStockout", "items为null或长度为零时onCreating委托不能为null");
                _onStockout = onStockout;
            } else {
                foreach (var item in items) {
                    _stack.Push(item);
                }
            }
        }

        #region IDisposable Members

        public void Dispose() {
            T item;
            while (_stack.TryPop(out item)) {
                var disposable = item as IDisposable;
                if (disposable != null)
                    disposable.Dispose();
            }
        }

        #endregion

        /// <summary>
        ///   尝试从池中获取一个项并封装为实现IReturnOnDisposed接口的结构。
        ///   对象使用完毕后调用结构的Dispose方法使对象回归到池中。
        ///   如池可用对象为零，则尝试调用onCreating回调获取。
        /// </summary>
        /// <returns></returns>
        public IReturnOnDisposed Take() {
            T item;
            if (_stack.TryPop(out item)) {
                Interlocked.Decrement(ref _available);
                Interlocked.Increment(ref _taken);
            } else {
                if (_onStockout != null) {
                    item = _onStockout();
                }

                if (Interlocked.Increment(ref _missed)%100000 == 0) {
                    var proportion = ((float) _taken)/(_taken + _missed);
                    if (proportion < 0.9) {
                        _log.Warn("池命中率为：" + proportion.ToString("G") + "，限制大小可能需要增加！当前共请求" + (_taken + _missed) + "次");
                    }
                }
            }

            return new ReturnOnDisposed {Item = item, Pool = this};
        }

        /// <summary>
        ///   尝试将传入对象放入池中，
        ///   如果池容量已达上限该对象将尝试调用Dispose方法后抛弃。
        /// </summary>
        /// <param name = "item"></param>
        private void Return(T item) {
            if (item == null)
                throw new ArgumentNullException();

            if (Interlocked.Increment(ref _available) < _limit) {
                _stack.Push(item);
            } else {
                Interlocked.Decrement(ref _available);

                var disposable = item as IDisposable;
                if (disposable != null)
                    disposable.Dispose();
            }
        }

        #region Nested type: IReturnOnDisposed

        /// <summary>
        ///   封装从池中获取的对象。
        ///   调用接口的Dispose方法时将对象归还到池中。
        /// </summary>
        public interface IReturnOnDisposed : IDisposable {
            T Item { get; }
        }

        #endregion

        #region Nested type: ReturnOnDisposed

        private struct ReturnOnDisposed : IReturnOnDisposed {
            public LockFreePool<T> Pool;

            #region IReturnOnDisposed Members

            public void Dispose() {
                if (Item != null)
                    Pool.Return(Item);
            }

            public T Item { get; set; }

            #endregion
        }

        #endregion
    }
}